package com.soup.lab.spring.web.advice;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.soup.lab.spring.web.tools.enums.validation.EnumCode;
import com.soup.lab.spring.web.tools.enums.basic.SelectEnum;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanGenerator;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMappingJacksonResponseBodyAdvice;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @deprecated 使用JsonSerializer处理，不必自己考虑属性的嵌套
 */
//@ControllerAdvice
@Deprecated
public class MyResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice implements EnvironmentAware {

    private String nestedCheckPackage;

    @Override
    protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
        Object ret = bodyContainer.getValue();
        if(ret == null) {
            return;
        }
        bodyContainer.setValue(doWithType(ret, returnType.getGenericParameterType(), Collections.newSetFromMap(new IdentityHashMap<>())));
    }

    private Object doWithObject(Object o, Set<Object> checkedObjSet) {
        return o == null ? null : doWithType(o, o.getClass(), checkedObjSet);
    }

    private Object doWithType(Object o, Type type, Set<Object> checkRecord) {
        if(type instanceof Class) {
            Class clazz = (Class) type;
            if(clazz.isArray()) {
                if(Array.getLength(o) > 0 && o instanceof Object[]) {
                    return handleArray((Object[]) o, checkRecord);
                }
                return o;
            } else if(Collection.class.isAssignableFrom(clazz)) {
                Collection collection = (Collection) o;
                if(collection.isEmpty()) {
                    return o;
                }
                return handleCollection(collection, checkRecord);
            }
            return handleOne(o, checkRecord);
        } else if(type instanceof ParameterizedType) {
            ParameterizedType retType = (ParameterizedType) type;
            Class rawType = (Class) retType.getRawType();
            return doWithType(o, rawType, checkRecord);
        }
        return o;
    }

    private Object[] handleArray(Object[] array, Set<Object> checkRecord) {
        Object[] result = new Object[array.length];
        Object o;
        for (int i = 0; i < array.length; i++) {
            o = array[i];
            if(checkRecord.contains(o)) {
                throw new IllegalArgumentException("检查数组时发现循环引用:"+ o);
            }
            result[i] = doWithObject(o, checkRecord);
        }
        return result;
    }

    private List handleCollection(Collection collection, Set<Object> checkRecord) {
        List result = new ArrayList();
        for (Object o : collection) {
            if(checkRecord.contains(o)) {
                throw new IllegalArgumentException("检查集合时发现循环引用:"+ o);
            }
            result.add(doWithObject(o, checkRecord));
        }
        return result;
    }

    private EnumObjectWrapper handleOne(Object one, Set<Object> checkRecord) {
        Class retType = one.getClass();
        Map<String, String> additionEnumDescMap = new HashMap<>();
        ReflectionUtils.doWithFields(retType, field -> handleEnumCodeField(field, retType, one, additionEnumDescMap));
        return new EnumObjectWrapper(one, additionEnumDescMap);
    }

    private void handleEnumCodeField(Field field, Class retType, Object value, Map<String, String> additionMap) {
        // 如果已经存在了描述字段，就不处理
        if(ReflectionUtils.findField(retType, field.getName()+"Name") != null) {
            return;
        }
        ReflectionUtils.makeAccessible(field);
        Object val = ReflectionUtils.getField(field, value);
        if(val == null) {
            return;
        }
        EnumCode enumCode;
        if((enumCode = field.getAnnotation(EnumCode.class)) != null) {
            Class<? extends Enum<?>> enumClass = enumCode.value();
            if (!SelectEnum.class.isAssignableFrom(enumClass)) {
                return;
            }
            Enum<?>[] enumConstants = enumClass.getEnumConstants();
            for (Enum<?> one : enumConstants) {
                SelectEnum selectEnum = (SelectEnum) one;
                if (selectEnum.codeEquals(val)) {
                    additionMap.put(field.getName() + "Name", selectEnum.getDesc());
                    return;
                }
            }
            // 没有的话设置为空串
            additionMap.put(field.getName() + "Name", "");
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        nestedCheckPackage = environment.getProperty("nested.check.package", getDefaultPackagePrefix(environment));
    }

    private boolean needNestedCheck(@NonNull Object val) {
        BeanGenerator generator = new BeanGenerator();
        generator.setSuperclass(val.getClass());
        generator.setClassLoader(val.getClass().getClassLoader());
        generator.addProperty("additionalMap", Map.class);
        try {
            Object o = generator.create();
            BeanCopier beanCopier = BeanCopier.create(val.getClass(), o.getClass(), false);
            beanCopier.copy(val, o, null);
        } catch (Exception e) {
            e.printStackTrace();
        }


        Package p = val.getClass().getPackage();
        return p != null && p.getName().startsWith(nestedCheckPackage);
    }

    private String getDefaultPackagePrefix(Environment environment) {
        Package currentPackage = getClass().getPackage();
        if(currentPackage != null) {
            return "";
        }
        int pointCount = environment.getProperty("nested.check.package.index", Integer.class, 2);
        // 取包名第二个.之前的部分作为默认的包前缀
        int i = 1;
        StringBuilder sb = new StringBuilder();
        for (char c : currentPackage.getName().toCharArray()) {
            if(c == '.' && i++ == pointCount) {
                return sb.toString();
            }
            sb.append(c);
        }
        return sb.toString();
    }

    public static class EnumObjectWrapper {
        private final Object target;
        private final Map<String, String> addition;

        public EnumObjectWrapper(Object target, Map<String, String> addition) {
            this.target = target;
            this.addition = addition;
        }

        @JsonUnwrapped
        public Object getTarget() {
            return target;
        }

        @JsonAnyGetter
        public Map<String, String> addition() {
            return addition;
        }

    }

}
